﻿using System;
using System.Globalization;
using System.IO;
using System.Net.Mail;
using System.Net.Mime;
using System.Text;
using Microsoft.SharePoint.Administration;
using log4net;
using Kailua.net.windward.utils;
using net.windward.api.csharp;
using net.windward.utils;
using ArrowCore;
using Microsoft.Win32;
using System.Collections.Generic;
using WindwardReportsDrivers.net.windward.datasource.xml;
using Kailua.net.windward.utils.ado.SPList.custom;
using net.windward.utils.ado;

namespace ArrowScheduleCore
{
    /// <summary>
    /// This runs a scheduled report.
    /// </summary>
    public class RunScheduledReport
    {

        private static readonly UTF8Encoding enc = new UTF8Encoding();
        private static readonly ILog log = LogManager.GetLogger(typeof(RunScheduledReport));
        private const string ARROW_EMAIL_SERVER_PROP = "WindwardArrowEmailServer";
        private const string ARROW_EMAIL_ADDR_PROP = "WindwardArrowEmailAddress";

        /// <summary>
        /// Runs a scheduled item using the given template and data source profile collection.
        /// </summary>
        /// <param name="item">the ScheduleItem to run</param>
        /// <param name="template">the template</param>
        /// <returns></returns>
        public static ScheduleRunItem Run(ScheduleItem item, Stream template, TemplateMetrics templateMetrics)
        {
            string reportFilename = null;
            StringBuilder reportEmails = new StringBuilder();
            Report rpt = null;

            try
            {
                string emailHost = (string)SPFarm.Local.Properties[ARROW_EMAIL_SERVER_PROP];
                string emailFrom = (string)SPFarm.Local.Properties[ARROW_EMAIL_ADDR_PROP];
                string saveFolder = item.SaveFolder;

                // create the report
                rpt = CreateReport(item, templateMetrics, template);
                MemoryStream data = (MemoryStream)rpt.GetReport();
                // Only need to save/emailFrom item if item wasn't printed.
                if (item.Format != Report.REPORT_TYPE.PRINTER)
                {
                    // email it
                    if (item.IsEmailTo)
                    #region email it
                    {
                        ReportHtml html = rpt as ReportHtml;
                        Attachment[] atts = (html != null) && (html.Bitmaps.Length > 0)
                                                ? new Attachment[html.Bitmaps.Length + 1]
                                                : new Attachment[1];

                        ContentType ct = new ContentType();
                        ct.MediaType = ExtensionConvert.ToContentType(item.Format);
                        ct.Name = item.Title + "." + ExtensionConvert.ToExtension(item.Format);
                        data.Position = 0;
                        atts[0] = new Attachment(data, ct);

                        MemoryStream[] bmpData = new MemoryStream[html == null ? 0 : html.Bitmaps.Length];
                        if ((html != null) && (html.Bitmaps.Length > 0))
                        {
                            int attInd = 0;
                            foreach (HtmlBitmap bmp in html.Bitmaps)
                            {
                                ct = new ContentType();
                                ct.MediaType = "image/" + bmp.Filename.Substring(bmp.Filename.LastIndexOf('.') + 1);
                                ct.Name = bmp.Filename;
                                bmpData[attInd] = new MemoryStream(bmp.Filedata);
                                atts[attInd + 1] = new Attachment(bmpData[attInd], ct);
                                attInd++;
                            }
                        }

                        SmtpClient mailObj = new SmtpClient();
                        mailObj.UseDefaultCredentials = true;
                        mailObj.Host = emailHost;
                        string[] addrs = item.EmailTo.Split(new[] { ';' });
                        string subject = String.IsNullOrEmpty(item.Description) ? item.Title : item.Description;
                        string txtMessage = String.Format(GlobalStrings.RunScheduledReport_runReportMailedRptMessage, subject, Environment.UserName,
                                                          Environment.MachineName);
                        // temporary change to display Arrow instead of AutoTag
                        txtMessage = String.Format(GlobalStrings.RunScheduledReport_emailReportSubject,
                                subject, Environment.UserName, Environment.MachineName);
                        byte[] htmlMessage = enc.GetBytes(String.Format(GlobalStrings.RunScheduledReport_emailReportBody,
                                subject, Environment.UserName, Environment.MachineName));
                        foreach (string msgTo in addrs)
                        {
                            string email = msgTo.Trim();
                            if (email.Length > 0)
                            {
                                MailMessage mail = new MailMessage(emailFrom, email, subject, txtMessage);
                                AlternateView htmlView = new AlternateView(new MemoryStream(htmlMessage), MediaTypeNames.Text.Html);
                                mail.AlternateViews.Add(htmlView);
                                foreach (Attachment attOn in atts)
                                    mail.Attachments.Add(attOn);
                                mailObj.Send(mail);
                                reportEmails.Append(email).Append(';');
                            }
                        }
                        foreach (MemoryStream bmp in bmpData)
                            bmp.Dispose();
                        log.Info(String.Format("Successfully emailed report {0} to recepients {1}", item.Title, item.EmailTo));
                    }
                    #endregion email it
                }

                item.RunResult = null;
                log.Info(String.Format("Successfully ran scheduled report {0} (file:{1})", item.Title, item.TemplateFileName ?? "unknown sp file"));

            }
            catch (Exception ex)
            {
                item.RunResult = ex.Message;
                log.Error(String.Format("Failed to run scheduled report {0} (file:{1})", item.Title, item.TemplateFileName ?? "unknown sp file"), ex);
            }
            ScheduleRunItem sriRet = new ScheduleRunItem(item, reportFilename, reportEmails.Length == 0 ? null : reportEmails.ToString());
            //sriRet.Report = rpt;
            if (rpt != null) // in case of an exception
                sriRet.ReportStream = rpt.GetReport();
            return sriRet;
        }

        /// <summary>
        /// Runs a scheduled item using the given template and data source profile collection.
        /// </summary>
        /// <param name="item">the ScheduleItem to run</param>
        /// <param name="template">the template</param>
        /// <param name="dataSources">the data source collection</param>
        /// <returns></returns>
        public static ScheduleRunItem Run(ScheduleItem item, Stream template, Dictionary<string, IReportDataSource> dataSources)
        {
            string reportFilename = null;
            StringBuilder reportEmails = new StringBuilder();
            Report rpt = null;

            try
            {
                string emailHost = (string)SPFarm.Local.Properties[ARROW_EMAIL_SERVER_PROP];
                string emailFrom = (string)SPFarm.Local.Properties[ARROW_EMAIL_ADDR_PROP];
                string saveFolder = item.SaveFolder;

                // create the report
                rpt = CreateReport(item, dataSources, template);
                MemoryStream data = (MemoryStream)rpt.GetReport();
                // Only need to save/emailFrom item if item wasn't printed.
                if (item.Format != Report.REPORT_TYPE.PRINTER)
                {
                    // email it
                    if (item.IsEmailTo)
                    #region email it
                    {
                        ReportHtml html = rpt as ReportHtml;
                        Attachment[] atts = (html != null) && (html.Bitmaps.Length > 0)
                                                ? new Attachment[html.Bitmaps.Length + 1]
                                                : new Attachment[1];

                        ContentType ct = new ContentType();
                        ct.MediaType = ExtensionConvert.ToContentType(item.Format);
                        ct.Name = item.Title + "." + ExtensionConvert.ToExtension(item.Format);
                        data.Position = 0;
                        atts[0] = new Attachment(data, ct);

                        MemoryStream[] bmpData = new MemoryStream[html == null ? 0 : html.Bitmaps.Length];
                        if ((html != null) && (html.Bitmaps.Length > 0))
                        {
                            int attInd = 0;
                            foreach (HtmlBitmap bmp in html.Bitmaps)
                            {
                                ct = new ContentType();
                                ct.MediaType = "image/" + bmp.Filename.Substring(bmp.Filename.LastIndexOf('.') + 1);
                                ct.Name = bmp.Filename;
                                bmpData[attInd] = new MemoryStream(bmp.Filedata);
                                atts[attInd + 1] = new Attachment(bmpData[attInd], ct);
                                attInd++;
                            }
                        }

                        SmtpClient mailObj = new SmtpClient();
                        mailObj.UseDefaultCredentials = true;
                        mailObj.Host = emailHost;
                        string[] addrs = item.EmailTo.Split(new[] { ';' });
                        string subject = String.IsNullOrEmpty(item.Description) ? item.Title : item.Description;
                        string txtMessage = String.Format(GlobalStrings.RunScheduledReport_runReportMailedRptMessage, subject, Environment.UserName,
                                                          Environment.MachineName);
                        // temporary change to display Arrow instead of AutoTag
                        txtMessage = String.Format(GlobalStrings.RunScheduledReport_emailReportSubject,
                                subject, Environment.UserName, Environment.MachineName);
                        byte[] htmlMessage = enc.GetBytes(String.Format(GlobalStrings.RunScheduledReport_emailReportBody,
                                subject, Environment.UserName, Environment.MachineName));
                        foreach (string msgTo in addrs)
                        {
                            string email = msgTo.Trim();
                            if (email.Length > 0)
                            {
                                MailMessage mail = new MailMessage(emailFrom, email, subject, txtMessage);
                                AlternateView htmlView = new AlternateView(new MemoryStream(htmlMessage), MediaTypeNames.Text.Html);
                                mail.AlternateViews.Add(htmlView);
                                foreach (Attachment attOn in atts)
                                    mail.Attachments.Add(attOn);
                                mailObj.Send(mail);
                                reportEmails.Append(email).Append(';');
                            }
                        }
                        foreach (MemoryStream bmp in bmpData)
                            bmp.Dispose();
                        log.Info(String.Format("Successfully emailed report {0} to recepients {1}", item.Title, item.EmailTo));
                    }
                    #endregion email it
                }

                item.RunResult = null;
                log.Info(String.Format("Successfully ran scheduled report {0} (file:{1})", item.Title, item.TemplateFileName ?? "unknown sp file"));

            }
            catch (Exception ex)
            {
                item.RunResult = ex.Message;
                log.Error(String.Format("Failed to run scheduled report {0} (file:{1})", item.Title, item.TemplateFileName ?? "unknown sp file"), ex);
            }
            ScheduleRunItem sriRet = new ScheduleRunItem(item, reportFilename, reportEmails.Length == 0 ? null : reportEmails.ToString());
            //sriRet.Report = rpt;
            if (rpt != null) // in case of an exception
                sriRet.ReportStream = rpt.GetReport();
            return sriRet;
        }

        /// <summary>
        /// Create the requested report. This will build the report object and run it.
        /// </summary>
        /// <param name="item">The report to run.</param>
        /// <param name="docData">The global data, mainly the parameters.</param>
        /// <param name="template">The template.</param>
        /// <returns>The generated report.</returns>
        public static Report CreateReport(ScheduleItem item, TemplateMetrics templateMetrics, Stream template)
        {
            // set up to ask for a report
            Report procRequest = null;
            try
            {
                procRequest = CreateReportGen(item, template);

                procRequest.ProcessSetup();

                // add data
                foreach (IDictionary<string, string> dataSourceInfo in templateMetrics.TemplateDatasources)
                {
                    IReportDataSource ds = GetIReportDataSource(dataSourceInfo);

                    try
                    {
                        SetDataSourceVarMap(item, ds);

                        string dsName = dataSourceInfo.ContainsKey("name") ? dataSourceInfo["name"] : null;
                        procRequest.ProcessData(ds, dsName);
                    }
                    finally
                    {
                        if (ds != null)
                            ds.Close();
                    }
                }

                procRequest.ProcessComplete();
                return procRequest;
            }
            catch (Exception)
            {
                if (procRequest != null)
                    procRequest.Dispose();
                throw;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <param name="dataSources"></param>
        /// <param name="template"></param>
        /// <returns></returns>
        public static Report CreateReport(ScheduleItem item, Dictionary<string, IReportDataSource> dataSources, Stream template)
        {
            // This method was created as a way for arrow scheduler to pass in the IReportDataSource
            // instead of having to modify XmlDataSourceInfo to retrieve xml files via sharepoint fba

            // set up to ask for a report
            Report procRequest = null;
            try
            {
                procRequest = CreateReportGen(item, template);

                procRequest.ProcessSetup();

                // add data
                foreach (KeyValuePair<string, IReportDataSource> dataSource in dataSources)
                {
                    IReportDataSource ds = dataSource.Value;

                    try
                    {
                        SetDataSourceVarMap(item, ds);

                        procRequest.ProcessData(ds, dataSource.Key);
                    }
                    finally
                    {
                        if (ds != null)
                            ds.Close();
                    }
                }

                procRequest.ProcessComplete();
                return procRequest;
            }
            catch (Exception)
            {
                if (procRequest != null)
                    procRequest.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Sets the <see cref="IReportDataSource.Map"/> based on the <see cref="ScheduleVarItem"/> values
        /// associated with the specified <see cref="ScheduleItem"/>.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="ds"></param>
        private static void SetDataSourceVarMap(ScheduleItem item, IReportDataSource ds)
        {
            // add maps
            DateTime now = DateTime.Now.Date;
            DateTimeFormatInfo frmt = item.Period.Culture.DateTimeFormat;
            Calendar cldr = item.Period.Culture.Calendar;
            KeyPairDictionary vars = new KeyPairDictionary();
            foreach (ScheduleVarItem svi in item.Variables)
                switch (svi.CalOffset)
                {
                    case ScheduleVarItem.CAL_OFFSET.FIXED:
                        vars.Add(svi.Name, svi.Value);
                        break;
                    case ScheduleVarItem.CAL_OFFSET.TODAY:
                        vars.Add(svi.Name, now.ToString(frmt.ShortDatePattern));
                        break;
                    case ScheduleVarItem.CAL_OFFSET.START_OF_WEEK:
                        DateTime week = now;
                        while (week.DayOfWeek != item.Period.Culture.DateTimeFormat.FirstDayOfWeek)
                            week = week.AddDays(-1);
                        vars.Add(svi.Name, week.ToString(frmt.ShortDatePattern));
                        break;
                    case ScheduleVarItem.CAL_OFFSET.START_OF_MONTH:
                        Trap.trap();
                        vars.Add(svi.Name, cldr.AddDays(now, 1 - now.Day).ToString(frmt.ShortDatePattern));
                        break;
                    case ScheduleVarItem.CAL_OFFSET.START_OF_QUARTER:
                        Trap.trap();
                        DateTime qrtr = cldr.AddDays(now, 1 - now.Day);
                        qrtr = cldr.AddMonths(qrtr, -(now.Month - 1) % 3);
                        vars.Add(svi.Name, qrtr.ToString(frmt.ShortDatePattern));
                        break;
                    case ScheduleVarItem.CAL_OFFSET.START_OF_YEAR:
                        Trap.trap();
                        DateTime year = cldr.AddDays(now, 1 - now.Day);
                        year = cldr.AddMonths(year, 1 - year.Month);
                        vars.Add(svi.Name, year.ToString(frmt.ShortDatePattern));
                        break;
                    default:
                        throw new ArgumentException("unknown offset " + svi.CalOffset);
                }
            ds.Map = vars;
        }

        /// <summary>
        /// Creates a <see cref="Report"/> object based on the report output type in <paramref name="item"/>.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="template"></param>
        /// <returns></returns>
        private static Report CreateReportGen(ScheduleItem item, Stream template)
        {
            switch (item.Format)
            {
                case Report.REPORT_TYPE.DOCX:
                    return new ReportDocx(template);
                case Report.REPORT_TYPE.HTML:
                    return new ReportHtml(template);
                case Report.REPORT_TYPE.PDF:
                    return new ReportPdf(template);
                case Report.REPORT_TYPE.PPTX:
                    return new ReportPptx(template);
                case Report.REPORT_TYPE.RTF:
                    return new ReportRtf(template);
                case Report.REPORT_TYPE.SPREAD_ML:
                    return new ReportSpreadML(template);
                case Report.REPORT_TYPE.TXT:
                    return new ReportText(template);
                case Report.REPORT_TYPE.WORD_ML:
                    return new ReportWordML(template);
                case Report.REPORT_TYPE.XLS:
                    return new ReportXls(template);
                case Report.REPORT_TYPE.XLSX:
                    return new ReportXlsx(template);
                default:
                    throw new ApplicationException("unknown report extension " + item.Format);
            }
        }

        /// <summary>
        /// Gets an IReportDataSource using data source info from an AutoTag template.
        /// </summary>
        /// <param name="dataSourceInfo">The set of data source attributes to use. This comes from TemplateMetrics.TemplateDatasources.
        /// See Report.GetMetrics().</param>
        /// <returns>The IReport data source.</returns>
        private static IReportDataSource GetIReportDataSource(IDictionary<string, string> dataSourceInfo)
        {
            IReportDataSource ds = null;

            try
            {
                //ProfileXml();
                // full-type values:
                // ADO.NET (including Windward SharePoint List): AdoDataSourceInfo
                // XML: XmlDataSourceInfo

                // protocol values:
                // REST
                // SHAREPOINT
                // STANDARD

                // provider-class:
                // Provider invariant name as provided to DbProviderFactories.GetFactory(string providerInvariantName) method in Kailua.

                if (dataSourceInfo["full-type"].Equals("XmlDataSourceInfo"))
                {
                    #region
                    string url = dataSourceInfo["url"];

                    #region XML CREDENTIALS_MODE
                    XPathDataSource.CREDENTIALS_MODE mode;
                    if (dataSourceInfo.ContainsKey("windows-identity"))
                        mode = XPathDataSource.CREDENTIALS_MODE.IDENTITY;
                    else if (dataSourceInfo.ContainsKey("protocol"))
                    {
                        string protocol = dataSourceInfo["protocol"];
                        switch (protocol)
                        {
                            case "STANDARD":
                                mode = XPathDataSource.CREDENTIALS_MODE.CREDENTIALS;
                                break;
                            case "SHAREPOINT":
                                mode = XPathDataSource.CREDENTIALS_MODE.SHAREPOINT;
                                break;
                            case "REST":
                                mode = XPathDataSource.CREDENTIALS_MODE.REST;
                                break;
                            default:
                                // exception
                                throw new Exception();
                                break;
                        }
                    }
                    else
                    {
                        mode = XPathDataSource.CREDENTIALS_MODE.CREDENTIALS;
                    }
                    #endregion XML CREDENTIALS_MODE

                    #region load XML creds
                    string username = null;
                    string password = null;
                    if (mode != XPathDataSource.CREDENTIALS_MODE.IDENTITY)
                    {
                        if (!dataSourceInfo.ContainsKey("credentials"))
                        {
                            username = dataSourceInfo["username"];
                            password = dataSourceInfo["password"];
                        }
                        else
                        {
                            // exception
                            throw new Exception();
                        }
                    }
                    #endregion load XML creds

                    string strSchema = dataSourceInfo.ContainsKey("schema") ? dataSourceInfo["schema"] : null;
                    bool hasSchema = strSchema != null && !strSchema.Equals("NONE");

                    if (!hasSchema)
                        return new XmlDataSourceImpl(url, mode, username, password);
                    else
                    {
                        string urlSchema = dataSourceInfo["schema-url"];
                        return new XmlDataSourceImpl(url, mode, username, password, urlSchema, mode, username, password);
                    }
                    #endregion
                }
                else if (dataSourceInfo["full-type"].Equals("AdoDataSourceInfo"))
                {
                    string providerClass = dataSourceInfo["provider-class"];
                    if (dataSourceInfo.ContainsKey("connection-string"))
                        return new AdoDataSourceImpl(providerClass, dataSourceInfo["connection-string"]);
                    else
                    {
                        WrVendor vendor = WrProviderFactories.CreateProvider(dataSourceInfo["provider-name"], null, providerClass);
                        WrCredentials creds;
                        if (dataSourceInfo.ContainsKey("windows-identity"))
                            creds = new WrCredentials();
                        else
                            creds = new WrCredentials(dataSourceInfo["username"], dataSourceInfo["password"]);
                        string connStr = vendor.ConnectionString(dataSourceInfo["server"], dataSourceInfo["database"], creds, true);
                        return new AdoDataSourceImpl(providerClass, connStr);
                    }
                }
                else if (dataSourceInfo["full-type"].Equals("CamlDataSourceInfo"))
                {
                    if (dataSourceInfo.ContainsKey("connection-string"))
                        return new AdoDataSourceImpl(typeof(SPListConnection), dataSourceInfo["connection-string"]);
                    else
                    {
                        WrVendor vendor = WrProviderFactories.CreateProvider("SharePoint List", null, "");
                        WrCredentials creds;
                        if (dataSourceInfo.ContainsKey("windows-identity"))
                            creds = new WrCredentials();
                        else
                            creds = new WrCredentials(dataSourceInfo["username"], dataSourceInfo["password"]);
                        string connStr = vendor.ConnectionString(dataSourceInfo["server"], dataSourceInfo["database"], creds, true);
                        return new AdoDataSourceImpl(typeof(SPListConnection), connStr);
                    }
                }
                else
                {
                    // throw exception and log
                    throw new Exception();
                }
            }
            finally
            {
                if (ds != null)
                    ds.Close();
            }
            return ds;
        }
    }
}
